<html>
    <head>
        <title>Cook-Torrance BRDF computed by shader</title>
        <style>
            body {
                font-family: Monospace;
                background-color: #f0f0f0;
                margin: 0px;
                overflow: hidden;
            }

            canvas {
                width: 100%;
                height: 100%;
            }
        </style>
        <script type="importmap">
            {
                "imports": {
                    "three": "./three.module.js",
                    "three/addons/": "./jsm/"
                }
            }
        </script>
    </head>

    <body>
        <script type="text/x-glsl" id="vertex">
            varying vec3 transformedNormal;
            varying vec3 pointPosition;
            varying vec3 lightVector;
            uniform vec3 pointLightPosition;

            void main()
            {
                transformedNormal = normalMatrix * normal;
                pointPosition = (modelViewMatrix * vec4( position, 1.0 )).xyz;
                vec4 lPosition = viewMatrix * vec4( pointLightPosition, 1.0 );
                lightVector = lPosition.xyz - pointPosition;
                gl_Position = projectionMatrix * vec4(pointPosition,1.0);
            }
        </script>

        <script type="text/x-glsl" id="ct-fragment">
            uniform vec3 lightPower;
            uniform vec3 ambient;
            uniform vec3 Kd; // 表面漫反射颜色
            uniform vec3 Ks; // 表面镜面反射颜色：等于R_F（0）
            uniform float m; // 材料粗糙度（微表面的平均斜率）
            uniform float s; // 镜面反射的入射光百分比

            varying vec3 transformedNormal;
            varying vec3 pointPosition;
            varying vec3 lightVector;

            #define PI 3.14159265

            float G(float NdotH, float NdotV, float VdotH, float NdotL)
            {
                float G1 = 2.0 * NdotH * NdotV / VdotH;
                float G2 = 2.0 * NdotH * NdotL / VdotH;
                return min( 1.0, min( G1, G2 ));
            }

            vec3 R_F(float VdotH) {
                return Ks + (1.0 - Ks)*pow(1.0-VdotH, 5.0);
            }

            float Beckmann(float NdotH){
                float A = 1.0 / (pow(m,2.0)+pow(NdotH,4.0)*PI);
                float B = exp( - pow( tan(acos(NdotH)) , 2.0) / pow(m,2.0));
                return A*B;
            }

            void main()
            {
                vec3  n = normalize( transformedNormal );
                vec3  v = normalize( -pointPosition );
                vec3  l = normalize(  lightVector );
                vec3  h = normalize( v+l );

                vec3 specular = vec3(0.0, 0.0, 0.0);
                float  NdotH = max(0.0, dot( n, h ));
                float  VdotH = max(0.0, dot( v, h ));
                float  NdotV = max(0.0, dot( n, v ));
                float  NdotL = max(0.0, dot( n, l ));
                if (NdotL > 0. && NdotV > 0.)
                {
                    specular = (Beckmann(NdotH) * G(NdotH, NdotV, VdotH, NdotL) * R_F(VdotH)) / ( NdotL* NdotV);
                }
                vec3 beta = lightPower / ( 4.0  * PI * pow( length(lightVector),2.0) );
                gl_FragColor = vec4(beta * NdotL * ((1.0-s)*Kd + s*specular) + ambient*Kd, 1.0);                
            }
        </script>
        <!--     gl_FragColor = vec4(vec3( Specular ), 1.0);          -->
        <script type="module">
            import * as THREE from "three";
            import OrbitControls from "./jsm/OrbitControls.js";

            var scene = new THREE.Scene();
            var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.copy(new THREE.Vector3(0, 2, -5));

            var renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setClearColor(0xf0f0f0);
            document.body.appendChild(renderer.domElement);

            let controls = new OrbitControls(camera, renderer.domElement);
            // controls.target.set(0, 0, 0);

            var uniforms = {
                Ks: { type: "v3", value: new THREE.Vector3() },
                Kd: { type: "v3", value: new THREE.Vector3() },
                ambient: { type: "v3", value: new THREE.Vector3(1, 1, 1) },
                pointLightPosition: { type: "v3", value: new THREE.Vector3() },
                lightPower: { type: "v3", value: new THREE.Vector3() },
                s: { type: "f", value: 0 },
                m: { type: "f", value: 0 },
            };

            var vs = document.getElementById("vertex").textContent;
            var fs = document.getElementById("ct-fragment").textContent;

            var material = new THREE.ShaderMaterial({ uniforms: uniforms, vertexShader: vs, fragmentShader: fs });

            var geometry = new THREE.SphereGeometry(1, 32, 16);
            var mesh = new THREE.Mesh(geometry, material);
            scene.add(mesh);

            {
                var geometry = new THREE.BoxGeometry(40, 0.1, 40);
                var mesh = new THREE.Mesh(geometry, material);
                mesh.position.set(4, -2, 0);
                scene.add(mesh);
            }

            let light = new THREE.Mesh(
                new THREE.SphereGeometry(1, 16 * 2, 16 * 2),
                new THREE.MeshBasicMaterial({ color: 0xffff00, wireframe: false })
            );
            light.position.copy(new THREE.Vector3(10.0, 10.0, 10.0));
            scene.add(light);

            // uniforms.Ks.value = new THREE.Vector3(0.95, 0.93, 0.88);
            uniforms.Ks.value = new THREE.Vector3(1,1,1);
            // uniforms.Kd.value = new THREE.Vector3(0.50754, 0.50754, 0.50754);
            uniforms.Kd.value = new THREE.Vector3(0.3,0.3,0.3);
            uniforms.ambient.value = new THREE.Vector3(1, 1, 1);
            uniforms.pointLightPosition.value = new THREE.Vector3(light.position.x, light.position.y, light.position.z);
            uniforms.lightPower.value = new THREE.Vector3(7000.0, 7000.0, 7000.0);
            uniforms.s.value = 0.8;
            uniforms.m.value = 0.1;

            function animate() {
                requestAnimationFrame(animate);
                render();
            }

            function render() {
                // controls.update();
                renderer.render(scene, camera);
            }

            animate();
        </script>
    </body>
</html>
